from BB_Observations import BB_Observations;
import scipy.stats as stats;
# from bayesian.bbn import build_bbn;
# from subprocess import call;
import pgm;
import warnings;

# --------------------------------------------------------------------------
# Simple Black Board (SBB)
# Example trivial blackboard from Corkill 1991
# ported from Lisp to python
# --------------------------------------------------------------------------


# the following are Globals used by the SBB

BLACKBOARD = {}
"""
Contains the blackboard database.
Version 1: simple dictionary of blackboard objects stored by name
"""

EVENTS = []
"""
Contains the events generated by each KSA execution.
The events are buffered until execution of the KSA is completed.
Then the events are processed by the control components.
"""

AGENDA = []
"""
Contains the list of activated KSs awaiting execution.
"""

CREATION_EVENT_KSS = []
"""
Contains KS specifications for all KSs that are interested in 
blackboard-object creation events 
(which is the only type of events currently supported).  
The define_creation_ks macro manages this list.
"""

TRACE_LEVEL = 3
"""
The current trace level.
Trace levels from 0 (none) to 3 (highest) are supported.
"""

# BB_Observations Instance
FILE_PATH_DB = '../../../data/PoL.s3db';
bb_Detections = BB_Observations(FILE_PATH_DB);

# --------------------------------------------------------------------------

counter = 0

def gensym(name = 'G'):
    """
    Create a generic name with a guaranteed unique name
    """
    global counter
    g = '{0}_{1}'.format(name, counter)
    counter += 1
    return g

def reset_gensym_counter():
    """
    Reset the gensym counter
    """
    global counter
    counter = 0
    print "counter = {0}".format(counter)

# --------------------------------------------------------------------------

class KS_spec(object):
    """
    Contains the information about a KS needed by the control machinery.
    object_types : list of object types of interest
    ks_function  : name of the function implementing the KS
    """
    global CREATION_EVENT_KSS
    def __init__(self, ks_type_name, object_types, ks_function):
        self.type_name = ks_type_name
        self.object_types = object_types
        self.ks_function = ks_function
        CREATION_EVENT_KSS.append(self)

class BB_object(object):
    """
    Contains the name and data of a blackboard object.
    """
    def __init__(self, name, data):
        self.name = name
        self.data = data
    def __repr__(self):
        return 'BB_object({0},{1})'.format(self.name, self.data)
    def __str__(self):
        return '<BB_object({0},{1})>'.format(self.name, self.data)

def reset_bb_system():
    """
    Resets the system to the initial state.
    """
    global BLACKBOARD, EVENTS, AGENDA
    BLACKBOARD = {}
    EVENTS = []
    AGENDA = []

def undefine_all_kss():
    """
    Removes all KS definitions
    """
    global CREATION_EVENT_KSS
    CREATION_EVENT_KSS = {}

def get_bb_object(name):
    """
    Trivial retrieval of blackboard object by name.
    'Name' must be a string.
    NOTE: This is not actually used in the example below.
          Also, this _could_ be much more fancy kind of pattern-based lookup
            depending on the blackboard repository structure.
    """
    global BLACKBOARD
    return BLACKBOARD[name].data

def creation_event(bb_object):
    """
    Control component code for processing a creation event.
    Determines which KSs are interested in the event and adds them to the agenda.
    NOTE: Does not evaluate the relative importance of activated KSs.
    """
    global CREATION_EVENT_KSS, AGENDA, TRACE_LEVEL
    
    bb_object_type = bb_object.type_name # bb_object.data.ks_type_name
    for ks_spec in CREATION_EVENT_KSS:
        if bb_object_type in ks_spec.object_types:
            ksa = ( ks_spec.ks_function, bb_object )
            if TRACE_LEVEL > 1:
                print "\tActivating {0}".format(ksa)
            AGENDA.append(ksa)

def signal_creation_event(bb_object):
    """
    Signals that 'bb_object' has been created.
    """
    global EVENTS
    EVENTS.append( (creation_event, bb_object.data) )

def make_bb_object(name, data):
    """
    Makes 'object' a blackboard object with name 'name' and signals a 
    creation event.  'Name' must be a string.
    """
    global BLACKBOARD, TRACE_LEVEL
    bb_obj = BB_object(name, data)
    if TRACE_LEVEL > 2:
        print "\tCreating {0} object: {1}".format( type(data), bb_obj )
    BLACKBOARD[name] = bb_obj
    signal_creation_event(bb_obj)
    return bb_obj

def db_event(data_event):
    """
    Control component code for processing a DB transaction event.
    For the moment, it just prints a message notifying about the transaction.
    """
    global CREATION_EVENT_KSS, AGENDA, TRACE_LEVEL
    
    eventType, sTblName = data_event[0], data_event[1];
    if TRACE_LEVEL > 1:
        print "\tDB Event {0} for table {1}".format(eventType, sTblName);
    for ks_spec in CREATION_EVENT_KSS:
        if eventType in ks_spec.object_types:
            ksa = ( ks_spec.ks_function, sTblName )
            if TRACE_LEVEL > 1:
                print "\tActivating {0}".format(ksa)
            AGENDA.append(ksa)

def signal_DB_event(eventType, sTblName):
    """
    Signals that an Event has happened into DB.
    """
    global EVENTS
    EVENTS.append( (db_event, [eventType, sTblName]) )

def store_table(dataFrame, sTblName):
    """
    Stores a table 'dataFrame' with name 'sTblName' into the DB and signals a 
    database transaction event.  'Name' must be a string.
    """
    global BLACKBOARD, TRACE_LEVEL;
    if TRACE_LEVEL > 2:
        print "\tStoring {0} table: {1}".format( sTblName, type(dataFrame) );
    bb_Detections.storeTable(dataFrame, sTblName);
    signal_DB_event("Update_Table_"+sTblName, sTblName);

EVENT_LIMIT = 10

def control_loop():
    """
    A trivial control loop.
    No opportunistic control is performed, simply LIFO stack (last-in, first-out) scheduling.
    The loop terminates when the agenda is empty.
    """
    global EVENTS, AGENDA, TRACE_LEVEL
    
    epoch = 0
    go = True
    while go:
        if TRACE_LEVEL > 0:
            print "[epoch {0}]".format(epoch)

        event_count = 0
        
        # process events
        for event in EVENTS:
            event_fn, event_data = event[0], event[1]
            if TRACE_LEVEL > 3:
                print "\t\tEvaluating event: ({0} {1})".format(event_fn, event_data)
            event_fn(event_data) # eval the function
            if TRACE_LEVEL > 3:
                print "\t\tEvent eval success."

            if event_count > EVENT_LIMIT:
                break
            event_count += 1
            
        EVENTS = []
        
        # check for stopping condition
        if AGENDA:
            # run the top KSA; LIFO stack agenda
            ksa = AGENDA.pop()
            if TRACE_LEVEL > 0:
                print "\tAGENDA Running: {0}".format(ksa)
            ksa_fn, ksa_data = ksa[0], ksa[1]
            if TRACE_LEVEL > 3:
                print "\t\tEvaluating KSA: ({0} {1})".format(ksa_fn, ksa_data)
            ksa_fn(ksa_data)
            if TRACE_LEVEL > 3:
                print "\t\tKSA eval success."
        else:
            go = False

        epoch += 1
    
    print("\n\nAgenda is empty.  Stopping.")


# --------------------------------------------------------------------------
# A simple "blackboard" application that reads raw data from sensors and
# generates observation datasets for inividual and group tracks. 
# --------------------------------------------------------------------------

class Raw_Data(object):
    """
    A blackboard object containing the Raw Data from Sensors.
    """
    type_name = 'Raw_Data'
    def __init__(self, value):
        self.value = value
    def __repr__(self):
        return 'Raw_Data({0})'.format(type(self.value))
    def __str__(self):
        return '<Raw_Data {0}>'.format(type(self.value))
    
class IndObs_Data(object):
    """
    A blackboard object containing the data about Individual Observation 
    Attributes generated from Generate_IndObs_KS.
    """
    type_name = 'IndObs_Data'
    def __init__(self, value):
        self.value = value
    def __repr__(self):
        return 'IndObs_Data({0})'.format(type(self.value))
    def __str__(self):
        return '<IndObs_Data {0}>'.format(type(self.value))
    
class GrpObs_Data(object):
    """
    A blackboard object containing the data about Groups Observation 
    Attributes generated from Generate_GrpObs_KS.
    """
    type_name = 'GrpObs_Data'
    def __init__(self, value):
        self.value = value
    def __repr__(self):
        return 'GrpObs_Data({0})'.format(type(self.value))
    def __str__(self):
        return '<GrpObs_Data {0}>'.format(type(self.value))
    
class IndGrp_Data(object):
    """
    A blackboard object containing the data about Individual & Groups Observation 
    Attributes generated from Generate_GrpObs_KS.
    """
    type_name = 'IndGrp_Data'
    def __init__(self, value):
        self.value = value
    def __repr__(self):
        return 'IndGrp_Data({0})'.format(type(self.value))
    def __str__(self):
        return '<IndGrp_Data {0}>'.format(type(self.value))
    
class PairObs_Data(object):
    """
    A blackboard object containing the data about Pair-wise Observation 
    Attributes generated from Generate_GrpObs_KS.
    """
    type_name = 'PairObs_Data'
    def __init__(self, value):
        self.value = value
    def __repr__(self):
        return 'PairObs_Data({0})'.format(type(self.value))
    def __str__(self):
        return '<PairObs_Data {0}>'.format(type(self.value))
    
class Gait_IndLM_Data(object):
    type_name = 'Gait_IndLM_Data'
    def __init__(self, value):
        self.value = value
    def __repr__(self):
        return 'Gait_IndLM_Data({0})'.format(type(self.value))
    def __str__(self):
        return '<Gait_IndLM_Data {0}>'.format(type(self.value))
    
class Gait_GrpLM_Data(object):
    type_name = 'Gait_GrpLM_Data'
    def __init__(self, value):
        self.value = value
    def __repr__(self):
        return 'Gait_GrpLM_Data({0})'.format(type(self.value))
    def __str__(self):
        return '<Gait_GrpLM_Data {0}>'.format(type(self.value))
    
# class 

# --------------------------------------------------------------------------
# The KS Definitions:
# NOTE: Because the SBB control scheme implements a simple LIFO ordering and
#       the KSs interested in a single type of event are activated in the
#       order in which they appear in the CREATION_EVENT_KSS list, changing
#       the order of definitions below will change the behavior of the app
# NOTE: A bit of a HACK: I define the KS's 'type_name' in the inherited 
#       KS_spec and passing the 'type_name' value as self.__class__.__name__
# --------------------------------------------------------------------------

class Generate_IndObs_KS(KS_spec):
    """ 
    KS that generates a dataset IndObs_Data about Individual Observation 
    Attributes obtained from using the dataset of Raw_Data from Sensors.
    """
    def __init__(self):
        KS_spec.__init__(self, self.__class__.__name__, ( Raw_Data.type_name, ), self)
    def __call__(self, bb_obj):
        dfRawData = bb_obj.value;
        dfIndAttrs = bb_Detections.getIndAttrs(dfRawData.copy());
        make_bb_object("IndObs_Data", IndObs_Data(dfIndAttrs));
        store_table(dfIndAttrs, "IndAttrs");
        
class Generate_GrpObs_KS(KS_spec):
    """ 
    KS that generates the datasets GrpObs_Data (about Groups Observation 
    Attributes) and Grp_Data (about Identifying Membership of each individual
    track to their respective group). Both obtained from using the 
    dataset of IndObs_Data generated from Generate_IndObs_KS.
    Then it generates the dataset IndGrp_Data (info about Group & Individual
    Observation's average positions).
    """
    def __init__(self):
        KS_spec.__init__(self, self.__class__.__name__, ( IndObs_Data.type_name, ), self)
    def __call__(self, bb_obj):
        dfIndAttrs = bb_obj.value;
        [dfGroups, dfGrp_Attrs] = bb_Detections.getGrpAttrs(dfIndAttrs);  # @UnusedVariable
#         make_bb_object("GrpObs_Data", GrpObs_Data([dfGroups, dfGrp_Attrs]));
        make_bb_object("GrpObs_Data", GrpObs_Data(dfGrp_Attrs));
        dfIndGrp_Attrs = bb_Detections.getIndGrp_Attrs(dfIndAttrs, dfGrp_Attrs);
        make_bb_object("IndGrp_Data", IndGrp_Data(dfIndGrp_Attrs));
        store_table(dfGrp_Attrs, "GrpAttrs");
        
class Generate_PairObs_KS(KS_spec):
    """ 
    KS that generates the dataset PairObs_Data about Pair-Wise Observation 
    Attributes for Groups and Individual tracks) obtained from using the 
    dataset of IndGrp_Data generated from Generate_GrpObs_KS.
    """
    def __init__(self):
        KS_spec.__init__(self, self.__class__.__name__, ( IndGrp_Data.type_name, ), self)
    def __call__(self, bb_obj):
        dfIndGrp_Attrs = bb_obj.value;
        dfPairAttrs = bb_Detections.getPairAttrs(dfIndGrp_Attrs);
        make_bb_object("PairObs_Data", PairObs_Data(dfPairAttrs));
        store_table(dfPairAttrs, "PairAttrs");
        
class Gait_IndLM_KS(KS_spec):
    def __init__(self):
        KS_spec.__init__(self, self.__class__.__name__, ( "Update_Table_IndAttrs", ), self)
        self.nThreshold = 0.1;
    def __call__(self, *args):
        dfIndAttrs = BLACKBOARD["IndObs_Data"].data.value; #!@UnusedVariable
        print "\tGait_IndLM_KS is Active."
        make_bb_object("Gait_IndLM_Data", Gait_IndLM_Data("Nothing yet..."));
        for nTrackId, dfTrack in dfIndAttrs.groupby(['TrackId']):  # @UnusedVariable
            avgSpeed = dfTrack["Speed"].sum()/float(dfTrack["Speed"].count());
            nProb_Run = self.getRun(avgSpeed);
            nProb_Stand = self.getStand(avgSpeed);
            nProb_Walk = self.getWalk(avgSpeed);
            aProbs = {"Stand":nProb_Stand, "Walk":nProb_Walk, "Run":nProb_Run};
            for sGait in aProbs:
                if aProbs[sGait]>self.nThreshold:
                    data = self.getData(dfTrack, nTrackId, avgSpeed, aProbs[sGait], sGait);
                    sAvgSpeed = "%.2f" % avgSpeed;
                    sProb = "%.2f" % aProbs[sGait];
                    if TRACE_LEVEL > 2:
                        print "\t** Creating New Data for ", [int(nTrackId), sGait, sProb, self.nThreshold, sAvgSpeed];
                    make_bb_object("Gait_IndLM_Data", Gait_IndLM_Data(data));
        
    def getData(self, dfTrack, nTrackId, avgSpeed, nProb_Gait, sGait):
        g = pgm.Graph();
        cpt1 = [.5, .5];
        cpt2 = {"['False']": [.5, .5],"['True']": [nProb_Gait, 1-nProb_Gait]};
        g.addnode(pgm.Node(sGait, ["False", "True"], [None], cpt1));
        g.addnode(pgm.Node("Speed=%.2f"%avgSpeed, ["False", "True"], [g.node[sGait]], cpt2));
        g.setup();
#         g.write2pdf("./Models/Graph_"+str(int(nTrackId))+"_"+str(sGait)+".pdf");
        data = {"TrackId":nTrackId, "Type":sGait, "Belief":nProb_Gait, 
                "Obs":["Speed"], "Obs_Vals":[avgSpeed], "MEs":["Walk","Stand"], 
                "Graph":g};
        return data;
        
    def getRun(self, avgSpeed):
        nRun = abs(64 - avgSpeed*10);
        nProb_Run = (1 - stats.beta.cdf(nRun, 3,4));
        if avgSpeed > 64: nProb_Run = 1;
        return nProb_Run;
    
    def getStand(self, avgSpeed):
        nStand = avgSpeed*10;
        nProb_Stand = (1 - stats.beta.cdf(nStand, 3,4));
        nProb_Stand = (0.6 + nProb_Stand*0.4);
        return nProb_Stand;
    
    def getWalk(self, avgSpeed):
        nWalk = abs(32 - avgSpeed*10);
        nProb_Walk = (1 - stats.beta.cdf(nWalk, 3,4));
        nProb_Walk = (0.6 + nProb_Walk*0.4);
        return nProb_Walk;
        
        
class Gait_GrpLM_KS(KS_spec):
    def __init__(self):
        KS_spec.__init__(self, self.__class__.__name__, ( "Update_Table_GrpAttrs", ), self)
        self.nThreshold = 0.1;
    def __call__(self, *args):
        dfGrp_Attrs = BLACKBOARD["GrpObs_Data"].data.value; #!@UnusedVariable
        print "\tGait_GrpLM_KS is Active."
        for nGrpId, dfGrp in dfGrp_Attrs.groupby(['GrpId']):  # @UnusedVariable
            avgSpeed = dfGrp["Speed"].sum()/float(dfGrp["Speed"].count());
            nProb_Run = self.getRun(avgSpeed);
            nProb_Stand = self.getStand(avgSpeed);
            nProb_Walk = self.getWalk(avgSpeed);
            aProbs = {"Stand":nProb_Stand, "Walk":nProb_Walk, "Run":nProb_Run};
            for sGait in aProbs:
                if aProbs[sGait]>self.nThreshold:
                    data = self.getData(dfGrp, nGrpId, avgSpeed, aProbs[sGait], sGait);
                    sAvgSpeed = "%.2f" % avgSpeed;
                    sProb = "%.2f" % aProbs[sGait];
                    if TRACE_LEVEL > 2:
                        print "\t** Creating New Data for ", [int(nGrpId), sGait, sProb, self.nThreshold, sAvgSpeed];
                    make_bb_object("Gait_GrpLM_Data", Gait_GrpLM_Data(data));
        
    def getData(self, dfTrack, nGrpId, avgSpeed, nProb_Gait, sGait):
        g = pgm.Graph();
        cpt1 = [.5, .5];
        cpt2 = {"['False']": [.5, .5],"['True']": [nProb_Gait, 1-nProb_Gait]};
        g.addnode(pgm.Node(sGait, ["False", "True"], [None], cpt1));
        g.addnode(pgm.Node("Speed=%.2f"%avgSpeed, ["False", "True"], [g.node[sGait]], cpt2));
        g.setup();
#         g.write2pdf("./Models/Graph_Grp_"+str(int(nGrpId))+"_"+str(sGait)+".pdf");
        data = {"GrpId":nGrpId, "Type":sGait, "Belief":nProb_Gait, 
                "Obs":["Speed"], "Obs_Vals":[avgSpeed], "MEs":["Walk","Stand"], 
                "Graph":g};
        return data;
        
    def getRun(self, avgSpeed):
        nRun = abs(64 - avgSpeed*10);
        nProb_Run = (1 - stats.beta.cdf(nRun, 3,4));
        if avgSpeed > 64: nProb_Run = 1;
        return nProb_Run;
    
    def getStand(self, avgSpeed):
        nStand = avgSpeed*10;
        nProb_Stand = (1 - stats.beta.cdf(nStand, 3,4));
        nProb_Stand = (0.6 + nProb_Stand*0.4);
        return nProb_Stand;
    
    def getWalk(self, avgSpeed):
        nWalk = abs(32 - avgSpeed*10);
        nProb_Walk = (1 - stats.beta.cdf(nWalk, 3,4));
        nProb_Walk = (0.6 + nProb_Walk*0.4);
        return nProb_Walk;

# --------------------------------------------------------------------------

# Instantiate KSs
Generate_IndObs_KS()
Generate_GrpObs_KS()
Generate_PairObs_KS()
Gait_IndLM_KS()
Gait_GrpLM_KS()

# --------------------------------------------------------------------------

def getRawData(nInitFrm=0, nEndFrm=8):
    warnings.filterwarnings("ignore");
    dfRawData = bb_Detections.getRawData(FILE_PATH_DB, nInitFrm, nEndFrm);
    make_bb_object("Raw_Data", Raw_Data(dfRawData));
    return dfRawData;

def run_application():
    """
    The top-level applicaiton function that runs (and reruns) the simple
    BB application.
    """
    reset_bb_system()
    getRawData(0, 8);
#     getRawData(9, 12);
    control_loop()
    
run_application();
import pprint;
print pprint.pprint(BLACKBOARD);
